JAVA 8 Lambda

一、基本语法

Java是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法能包含所需的代码。如下:

1
2
3
4
5
6
7
8
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return first.length - second.first;
}
}
// uses
Arrays.sort(strings, new LengthComparator());

如果使用Lambda表达式,则使用代码更改为:

1
Arrays.sort(strings, (String first, String second) -> { return first.length - second.first; }));

简化

在这里,编译器可以推导出first和second必然是字符串,参数类型可以省略不写;方法体里只有一行,亦可简化。如下:

1
Arrays.sort(strings, (first, second) -> first.length() - second.length());

无参

即使lambda表达式没有参数,仍然要提供括号,就像无参数方法一样:

1
() -> { for (int i = 100; i >= 0; i--) System.out.println(i); }

一参

如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至还可以省略小括号:

1
ActionListener listener = event -> System.out.println("The time is " + new Date());

注意

1)无需指定lambda表达式的返回类型,因为返回类型总是会由上下文推导得出。
2)如果一个lambda表达式只在某些分支返回一个值,而在另外一些分支不返回值,这是不合法的。例如,(int x) -> { if (x >= 0) return 1; }就不合法。

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.xian.lambda;
import java.util.Arrays;
import java.util.Date;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class LambdaTest {
public static void main(String[] args) {
String[] masters = {"liyao", "zhaoyu", "hufeng", "xianxiaotao", "cuihongquan"};
System.out.println(Arrays.toString(masters));
System.out.println("Sorted in dictionary order:");
Arrays.sort(masters);
System.out.println(Arrays.toString(masters));
System.out.println("Sorted by length:");
Arrays.sort(masters, (first, second) -> first.length() - second.length());
System.out.println(Arrays.toString(masters));
Timer t = new Timer(10000, event -> System.out.println("The time is" + new Date()));
t.start();
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}

二、函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这个接口称为函数式接口。如上例中的Comparator接口,或者java.util.function包中的接口Predicate:

1
2
3
4
public interface Predicate<T> {
boolean test(T t);
// Additional default and static methods
}

ArrayList类有一个removeIf方法它的参数就是一个Predicate。这个接口专门用来传递lambda表达式。

1
list.removeIf(e -> e == null);

三、方法引用

表达式System.out::println是一个方法引用,等价于lambda表达式x -> System.out.println(x)。又如下列两行代码等同:

1
2
Timer t = new Timer(1000, event -> System.out.println(event));
Timer t = new Timer(1000, System.out::println);

操作符 “::” 用来分割方法名与对象或类名。主要分为三种情况:

1
2
3
objcet::instanceMethod 如:System.out::println x -> System.out.println(x)
Class::staticMethod 如:Math::pow (x, y) -> Math.pow(x, y)
Class::instanceMethod 如:String::compareToIgnoreCase (x, y) -> x.compareToIgnoreCase(y)

对于第三种情况,第一个参数会成为方法的目标。

1
2
Arrays.sort(masters, String::compareToIgnoreCase);
Arrays.sort(masters, (first, second) -> first.compareToIgnoreCase(second));

注意:对于重载方法,编译器会尝试从上下文中找出你指的那一个方法;方法引用不能独立存在,总是会转换为函数式接口的实例;方法引用中可以使用this和super参数,例如this::equals等同于x -> this.equals(x);使用super作为目标,会调用方法的超类版本,格式为super::instanceMethod;this是指创建lambda表达式的方法所属的对象,super同理。

四、构造器引用

构造器引用与方法引用类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用。至于哪一个构造器,这取决于上下文。假如你有一个字符串列表,并把它转换为Person对象数组,其如下:

1
2
3
ArrayList<String> names = ...;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());

根据上下文,编译器会选择一个String参数的构造器。可以用数组类型建立构造器引用。例如,int[]::new,这等价于lambda表达式x -> new int[x]。

五、变量

lambda表达式中捕获的变量必须实际上是最终变量,即这个变量初始化之后就不会再为它赋新值。如下例中的text总是指示同一个String对象,所以此处合法。

1
2
3
4
5
6
7
8
9
10
11
12
public class LambdaTest {
public static void main(String[] args) {
repeatMessage("hello", 1000);
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
public static void repeatMessage(String text, int delay) {
ActionListener listener = event -> System.out.println(text);
new Timer(delay, listener).start();
}
}

当lambda表达式捕获外围作用域中变量的,无论是外部改变还是内部改变都不合法的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 外部改变:
public static void repeat(String text, int count) {
for (int i = 1; i <= count; i++) {
ActionListener listener = event -> {
System.out.println(i + ": " + text); // Error: Cannot refer to changing i
};
new Timer(1000, listener).start();
}
}
// 内部改变:
public static void countDown(int start, int delay) {
ActionListener listener = event -> {
start--; // Error: Cannot mutate captured variable
System.out.println(start);
};
new Timer(delay, listener).start();
}